package com.i360day.invoker.registry;

import com.i360day.invoker.annotation.RemoteClientEntity;
import com.i360day.invoker.common.*;
import com.i360day.invoker.hystrix.FallbackFactory;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.env.AbstractPropertyResolver;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.util.Assert;

import java.net.URI;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

/**
 * <p> @description:     <p>
 * <p>
 * <p> @author: 胡.青牛   <p>
 * <p>
 * <p> @date: 2019/4/30 0030  13:04
 **/
abstract class AbstractRemoteScanAnnotationParser extends AbstractPropertyResolver implements BeanDefinitionRegistrarFactory {
    private ResourceLoader resourceLoader;
    private Environment environment;
    private Map<Class, Object> cacheBeanRegisters = new ConcurrentHashMap();

    public AbstractRemoteScanAnnotationParser(ResourceLoader resourceLoader, Environment environment) {
        this.resourceLoader = resourceLoader;
        this.environment = environment;
    }

    @Override
    protected String getPropertyAsRawString(String key) {
        return environment.getProperty(key);
    }

    @Override
    public <T> T getProperty(String key, Class<T> targetType) {
        return environment.getProperty(key, targetType);
    }

    public ResourceLoader getResourceLoader() {
        return resourceLoader;
    }

    public Environment getEnvironment() {
        return environment;
    }

    public Map<Class, Object> getCacheBeanRegisters() {
        return cacheBeanRegisters;
    }

    /**
     * 注册bean，并添加到缓存
     *
     * @param registry
     * @param beanDefinitionHolder
     */
    public void registerBean(Class clazz, BeanDefinitionRegistry registry, BeanDefinitionHolder beanDefinitionHolder) {
        if (beanDefinitionHolder == null || registry == null) return;

        BeanDefinitionReaderUtils.registerBeanDefinition(beanDefinitionHolder, registry);
        cacheBeanRegisters.put(clazz, beanDefinitionHolder);
    }

    /**
     * 根据占位符获取配置文件中的值
     *
     * @param pro
     * @return
     */
    protected String parsePlaceHolder(String pro) {
        return parsePlaceHolder(pro, null);
    }

    /**
     * 根据占位符获取配置文件中的值
     *
     * @param pro
     * @param defaultValue
     * @return
     */
    protected String parsePlaceHolder(String pro, String defaultValue) {
        if (StringUtils.isEmpty(pro)) return defaultValue;
        try {
            String value = resolveRequiredPlaceholders(pro);
            if (StringUtils.isNotEmpty(value)) {
                return value;
            }
        } catch (Exception ex) {
            //ignore
        }
        return defaultValue;
    }

    /**
     * 扫描包下的class
     *
     * @return
     */
    protected final ClassPathScanningCandidateComponentProvider getScanner() {
        return getScanner((AnnotatedBeanDefinition beanDefinition) -> beanDefinition.getMetadata().isIndependent() && beanDefinition.getMetadata().isInterface());
    }

    /**
     * 扫描包下的class
     *
     * @return
     */
    protected final ClassPathScanningCandidateComponentProvider getScanner(Function<AnnotatedBeanDefinition, Boolean> function) {
        return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
            @Override
            protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                return function.apply(beanDefinition);
            }
        };
    }

    /**
     * <p> @author liju.z <p>
     *
     * <p> @Description 获取包名 <p>
     *
     * <p> @Date  9:07 <p>
     *
     * <p> @Param [importingClassMetadata] <p>
     *
     * <p> @return [importingClassMetadata] <p>
     **/
    protected String[] getPackageName(AnnotationMetadata importingClassMetadata) {
        if (importingClassMetadata instanceof StandardAnnotationMetadata) {
            Package packages = ((StandardAnnotationMetadata) importingClassMetadata).getIntrospectedClass().getPackage();
            return packages == null ? null : new String[]{packages.getName()};
        }
        return new String[]{ClassUtils.getClass(importingClassMetadata.getClassName()).getPackageName()};
    }

    /**
     * <p> @Description: 制定bean创建规则   <p>
     *
     * <p> @author: 胡.青牛   <p>
     *
     * <p> @Date:   2019/5/11 0011 16:36   <p>
     *
     * <p> @param null     <p>
     *
     * <p> @return:      <p>
     **/
    protected String getBeanName(Class<?> clazz) {
        return clazz.getName() + "." + HttpInvokerSpecification.class.getSimpleName();
    }

    /**
     * 获取客户端信息
     *
     * @param attributes
     * @return
     */
    protected RemoteClientEntity parseRemoteClient(Map<String, Object> attributes) {
        RemoteClientEntity entity = new RemoteClientEntity();
        //分组
        String group = attributes.get("group") + "";
        if (StringUtils.isEmpty(group)) {
            group = "";
        } else if (group.startsWith("/")) {
            group = group.substring(1);
        }
        entity.setGroup(parsePlaceHolder(group));
        //版本
        String version = attributes.get("version") + "";
        if (StringUtils.isEmpty(version)) {
            version = "";
        } else if (version.startsWith("/")) {
            version = version.substring(1);
        }
        entity.setVersion(parsePlaceHolder(version));
        //name
        String name = attributes.get("name") + "";
        if (StringUtils.isEmpty(attributes.get("name"))) {
            name = parsePlaceHolder("${spring.application.name}");
        }
        entity.setName(parsePlaceHolder(name));
        //ip
        String address = parsePlaceHolder(attributes.get("address") + "", attributes.get("address") + "");
        if (StringUtils.isEmpty(address) || (address.startsWith("${") && address.endsWith("}"))) {
            //获取全局配置
            address = parsePlaceHolder("${spring.invoker.server.address}");
            if (StringUtils.isEmpty(address)) {
                address = "unknown";
            }
        }
        //address禁止随意填写，只能使用IP+PORT方式
        if (StringUtils.startsWith(address, "http") || StringUtils.startsWith(address, "ws")) {
            try {
                logger.warn("remoteModule/remoteClient in address Prohibit using HTTP/WS prefix，Will be ignored as (IP + PORT)");
                address = URI.create(address).getAuthority();
            } catch (Exception ex) {
                //ignore
            }
        }

        entity.setAddress(address);
        //项目名
        String contextPath = parsePlaceHolder(attributes.get("contextPath") + "");
        contextPath = StringUtils.isEmpty(contextPath) || contextPath.startsWith("/") ? contextPath : "/" + contextPath;
        entity.setContextPath(contextPath);
        //回调
        entity.setFallback((Class<?>) attributes.get("fallback"));
        entity.setFallbackFactory((Class<? extends FallbackFactory>) attributes.get("fallbackFactory"));
        return entity;
    }

    /**
     * 获取serverUrl
     *
     * @param interfaceClazz
     * @param attributes
     * @return
     */
    protected URI getServerUrl(Class<?> interfaceClazz, Map<String, Object> attributes, boolean ignoreContextPath) {
        RemoteClientEntity remoteClientEntity = parseRemoteClient(attributes);
        //ip
        String address = remoteClientEntity.getAddress();
        if (StringUtils.isEmpty(address) || "unknown".equals(remoteClientEntity.getAddress())) {
            logger.warn(String.format("%s %s value is null，Please check the configuration", interfaceClazz, attributes.get("address") + ""));
            //获取名称
            address = remoteClientEntity.getName();
            Assert.isTrue(StringUtils.isNotEmpty(address), String.format("%s Get [ @RemoteClient or @RemoteModule ] name/address empty, please check your configuration !", interfaceClazz));
            //设置默认值
            if (StringUtils.isEmpty(address)) {
                address = "unknown";
            }
        }
        //请求地址
        String serviceUrl = HttpInvokerUrlUtils.getAssembleUrl(address, remoteClientEntity.getGroup(), remoteClientEntity.getVersion());
        //项目名
        String contextPath = remoteClientEntity.getContextPath();
        contextPath = StringUtils.isEmpty(contextPath) || contextPath.startsWith("/") ? contextPath : "/" + contextPath;
        //重组url
        return URI.create(HttpInvokerUrlUtils.getUrl(serviceUrl, ignoreContextPath ? null : contextPath, interfaceClazz));
    }

    /**
     * 注册remote bean
     *
     * @param registry
     * @param interfaceClazz
     * @param attributes
     * @param clientProxyClass
     * @param serverProxyClass
     */
    protected void registerRemoteBean(BeanDefinitionRegistry registry, Class<?> interfaceClazz, Map<String, Object> attributes, Class<?> clientProxyClass, Class<?> serverProxyClass) {
        //判断当前工程中，该接口是否已在spring上下文中存在
        if (!BeanDefinitionRegistryUtils.containsBeanName(registry, interfaceClazz)) {
            registerInterfaceBean(registry, interfaceClazz, attributes, clientProxyClass);
        }
        //通过接口获取到service实现类，注册到beanMappingHandler上。并且当前bean不是HttpInvokerClientFactoryBean代理的class
        else if (!clientProxyClass.equals(BeanDefinitionRegistryUtils.getBeanClass(registry, interfaceClazz))) {
            registerServerBean(registry, interfaceClazz, attributes, serverProxyClass);
        } else {
            //ignore
        }
    }

    /**
     * 注册interface bean
     *
     * @param registry
     * @param interfaceClazz
     * @param attributes
     * @param clientProxyClass
     */
    protected void registerInterfaceBean(BeanDefinitionRegistry registry, Class<?> interfaceClazz, Map<String, Object> attributes, Class<?> clientProxyClass) {
        //获取服务端地址
        URI serverUrl = getServerUrl(interfaceClazz, attributes, false);

        String clazzName = getBeanName(interfaceClazz);
        //已经存在了则不添加了
        if (!BeanDefinitionRegistryUtils.containsBeanName(registry, clazzName)) {
            //创建代理bean
            BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clientProxyClass);
            definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            definition.addConstructorArgValue(parseRemoteClient(attributes));
            definition.addPropertyValue("serviceUrl", serverUrl.toString());
            definition.addPropertyValue("serviceInterface", interfaceClazz);
            definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
            //attribute
            AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
            // has a default, won't be null
            beanDefinition.setPrimary(Boolean.valueOf(attributes.get("primary") + ""));
            BeanDefinitionHolder beanDefinitionHolder = new BeanDefinitionHolder(beanDefinition, clazzName, new String[]{clazzName});
            registerBean(interfaceClazz, registry, beanDefinitionHolder);
        }
    }

    /**
     * 注册server bean
     *
     * @param registry
     * @param interfaceClazz
     * @param attributes
     * @param serverProxyClass
     */
    protected void registerServerBean(BeanDefinitionRegistry registry, Class<?> interfaceClazz, Map<String, Object> attributes, Class<?> serverProxyClass) {
        //url
        URI serverUrl = getServerUrl(interfaceClazz, attributes, true);
        //已注册过，则忽略
        if (!BeanDefinitionRegistryUtils.containsBeanName(registry, serverUrl.getPath())) {
            BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(serverProxyClass);
            beanDefinitionBuilder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            beanDefinitionBuilder.addPropertyReference("service", BeanDefinitionRegistryUtils.getBeanName0(registry, interfaceClazz));
            beanDefinitionBuilder.addPropertyValue("serviceInterface", interfaceClazz);

            BeanDefinitionHolder beanDefinitionHolder = new BeanDefinitionHolder(beanDefinitionBuilder.getBeanDefinition(), serverUrl.getPath(), new String[]{interfaceClazz.getName()});
            registerBean(interfaceClazz, registry, beanDefinitionHolder);
        }
    }
}
